﻿//////////////////////////////////////////////////////////////////////////////
//
//  Copyright © 1998-2024 Glodon Company Limited.  All rights reserved.
//
//  Use of this software is subject to the terms of the Glodon license
//  agreement provided at the time of installation or download, or which
//  otherwise accompanies this software in either electronic or hard copy form.  
//
//////////////////////////////////////////////////////////////////////////////


#pragma once
#include "GnufBase.h"
#include "GnufItemDeclaration.h"
#include "GnufObjectPtr.h"

namespace gnuf {

    class GNUF_BASE_EXPORT IInputControlHandler : public gcmp::WeakReferenceable
    {
        DEFINE_CAST_BASE(gnuf::IInputControlHandler)
    };

    class InputControl;
    class GNUF_BASE_EXPORT InputControlAdaptation : public gcmp::WeakReferenceable
    {
        CANNOT_COPY_OR_ASSIGN(InputControlAdaptation)
    protected:
        InputControlAdaptation(EItemType);
        virtual ~InputControlAdaptation();

    public:
        inline InputControl * inputControl() const {
            return m_inputControl;
        }

        virtual void onVisibilityChanged(bool) = 0;
        virtual void onAbilityChanged(bool) = 0;
        virtual void onSelectionChanged(bool) = 0;
        virtual void onCollectLocation(std::set<const gcmp::UniqueString *> &) const = 0;
        virtual void onConnectedWith(InputControl *) {}

    private:
        friend class InputControl;
        const EItemType m_type;
        InputControl * m_inputControl;
    };

    class GNUF_BASE_EXPORT InputControl : public gcmp::WeakReferenceable
    {
        CANNOT_COPY_OR_ASSIGN(InputControl)
    protected:
        InputControl(EItemType, IInputControlHandler *);
        virtual ~InputControl();

    public:
        inline EItemType type() const { return m_type; }
        inline IInputControlHandler * inputControlHandler() const {
            return m_inputControlHandler;
        }

        // Subscribe/Unsubscribe an adaptation to this input control.
        // [Note] The type of the adaptation must match with the one of the 
        // input control. Otherwise, failed to subscribe.
        void   subscribe(InputControlAdaptation *);
        void unsubscribe(InputControlAdaptation *);

        void setVisible(bool);
        bool isVisible() const { return m_visible; }

        void setEnabled(bool);
        bool isEnabled() const { return m_enabled; }

        void setSelected(bool);
        bool isSelected() const { return m_selected; }

        template <class TDerivedInputControl>
        TDerivedInputControl * cast() {
            if (TDerivedInputControl::Type == m_type) {
                return static_cast<TDerivedInputControl *>(this);
            }
            return nullptr;
        }
        template <class TDerivedInputControl>
        const TDerivedInputControl * cast() const {
            if (TDerivedInputControl::Type == m_type) {
                return static_cast<const TDerivedInputControl *>(this);
            }
            return nullptr;
        }

        bool getLocations(std::set<const gcmp::UniqueString *> &) const;
        bool doesLocateAt(const gcmp::UniqueString *) const;

    protected:
        template <class TAdaptation, class Func>
        void notify(const Func& func, const TAdaptation * trigger)
        {
            GNUF_WATCHING_THIS;

            // Cache temporarily, and work with the cached, to avoid the crash
            // that might happen if an adaptation does unsubscribe from me. 
            auto ones = m_adaptations;
            for (auto one : ones)
            {
                if (trigger != one) {
                    func(static_cast<TAdaptation *>(one));
                }

                GNUF_IF_INVALID_RETURN_VOID;
            }
        }

        template <class TAdaptation, class Func>
        void notify(const Func& func, const TAdaptation* trigger) const
        {
            // Cache temporarily, and work with the cached, to avoid the crash
            // that might happen if an adaptation does unsubscribe from me. 
            auto ones = m_adaptations;
            for (auto one : ones) {
                if (trigger != one) {
                    func(static_cast<TAdaptation*>(one));
                }
            }
        }

        template <class TAdaptation>
        void notify(void (TAdaptation::*fire)(), const TAdaptation * trigger)
        {
            GNUF_WATCHING_THIS;

            // Cache temporarily, and work with the cached, to avoid the crash
            // that might happen if an adaptation does unsubscribe from me. 
            auto ones = m_adaptations;
            for (auto one : ones)
            {
                if (trigger != one) {
                    (static_cast<TAdaptation *>(one)->*fire)();
                }

                GNUF_IF_INVALID_RETURN_VOID;
            }
        }

        template <class TAdaptation, class TParameter>
        void notifyWithParameter(void (TAdaptation::*fire)(TParameter),
            TParameter parameter, const TAdaptation * trigger)
        {
            GNUF_WATCHING_THIS;

            // Cache temporarily, and work with the cached, to avoid the crash
            // that might happen if an adaptation does unsubscribe from me. 
            auto ones = m_adaptations;
            for (auto one : ones)
            {
                if (trigger != one) {
                    (static_cast<TAdaptation *>(one)->*fire)(parameter);
                }

                GNUF_IF_INVALID_RETURN_VOID;
            }
        }

        template <class TAdaptation, class TParameter>
        void notifyWithConstParameter(void (TAdaptation::*fire)(const TParameter &),
            const TParameter & parameter, const TAdaptation * trigger)
        {
            GNUF_WATCHING_THIS;

            // Cache temporarily, and work with the cached, to avoid the crash
            // that might happen if an adaptation does unsubscribe from me. 
            auto ones = m_adaptations;
            for (auto one : ones)
            {
                if (trigger != one) {
                    (static_cast<TAdaptation *>(one)->*fire)(parameter);
                }

                GNUF_IF_INVALID_RETURN_VOID;
            }
        }

        template <class TAdaptation, class TParameter, class TParameter2>
        void notifyWithParameter2(void (TAdaptation::*fire)(TParameter, TParameter2),
            TParameter parameter, TParameter2 parameter2, const TAdaptation * trigger)
        {
            GNUF_WATCHING_THIS;

            // Cache temporarily, and work with the cached, to avoid the crash
            // that might happen if an adaptation does unsubscribe from me. 
            auto ones = m_adaptations;
            for (auto one : ones)
            {
                if (trigger != one) {
                    (static_cast<TAdaptation *>(one)->*fire)(parameter, parameter2);
                }

                GNUF_IF_INVALID_RETURN_VOID;
            }
        }

    private:
        const EItemType m_type;
        IInputControlHandler * m_inputControlHandler;
        typedef std::set<InputControlAdaptation *> Adaptations;
        Adaptations m_adaptations;
        bool m_visible;
        bool m_enabled;
        bool m_selected;
    };

}
